<!DOCTYPE html>
<html>
<!--
    let、const、var区别
    字面量增强写法
    多种for循环遍历
    高级函数filter、map、reduce
    箭头表达式
    prototype原型
    箭头函数中this的指向
    Promise的基本使用
    数组、对象的解构
    数组的特殊用法
 -->

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <button>按钮5</button>
    <script>
        /* let/const/var区别 */
        {
            // var没有块级作用域，可以在块外使用
            var ck = 'CK';
            console.log(ck);
        }
        //块外使用
        console.log(ck);
        /* for引起的问题（if也有这个问题），无论点哪个按钮都是显示第5个 */
        /* 在以前可以使用闭包（相当于调用外部函数）来解决问题 */
        // var btns = document.getElementsByTagName('button');
        // for (var i = 0; i < btns.length; i++) {
        //     /* 引起错误的写法 */
        //     // btns[i].addEventListener('click', function () {
        //     //     console.log('第' + i + '个按钮被点击了');
        //     // });
        //     /* 闭包写法，太过复杂。不会出错是因为函数是一个作用域 */
        //     (function (num) {
        //         btns[num].addEventListener('click', function () {
        //             console.log('第' + num + '个按钮被点击了');
        //         });
        //     })(i);
        // }
        /* 换成let后，有块级作用域，不存在以上问题 */
        const btns = document.getElementsByTagName('button');
        for (let i = 0; i < btns.length; i++) {
            btns[i].addEventListener('click', function () {
                console.log('第' + i + '个按钮被点击了');
            });
        }
        /* const常量 */
        const b = 1;
        console.log(b);
        // a = 2;//常量不可再被赋值，会报错
        const obj = {
            name: 'ck',
            gender: 'man',
            year: '1998'
        }
        console.log(obj);
        //但是常量对象里面的值可以被修改
        obj.name = 'txf';
        obj.gender = 'women';
        obj.year = '1996'
        console.log(obj);

        /* 对象字面量的增强写法 */
        const name = 'ck';
        const gender = 'man';
        const year = 1998;
        // 普通写法
        const obj2 = {
            name: name,
            gender: gender,
            yaer: year,
        }
        console.log(obj2);
        // 增强写法
        const obj3 = {
            name,
            gender,
            year,
        }
        console.log(obj3);
        //函数对象的普通写法
        const obj4 = {
            abc: function () {

            },
        }
        //函数对象的增强写法
        const obj5 = {
            abc() {

            },
        }

        /* 3种for循环遍历 */
        let str = ['a', 'b', 'c', 'd'];
        //普通for循环遍历
        let strs = '';
        for (let i = 0; i < str.length; i++) {
            strs += str[i];
        }
        console.log(strs);
        strs = '';
        //for-in遍历，取出下标
        for (index in str) {
            strs += str[index];
        }
        console.log(strs);
        strs = '';
        //for-of遍历，取出对象
        for (item of str) {
            strs += item;
        }
        console.log(strs);

        /* js高级函数filter/map/reduce */
        let num = [12, 88, 23, 45, 76, 87, 40, 8];
        /* 普通写法 */
        // 需求1：找出小于50的数字
        // let num2 = [];
        // for (item of num) {
        //     if (item < 50) {
        //         num2.push(item);
        //     }
        // }
        // console.log(num2);
        // // 需求2：数值x2
        // let num3 = [];
        // for (item of num2) {
        //     num3.push(item * 2);
        // }
        // console.log(num3);
        // // 需求3：相加
        // let total = 0;
        // for (item of num3) {
        //     total += item;
        // }
        // console.log(total);
        /* 高级写法 */
        //使用filter函数
        // 传入一个函数，函数内写执行逻辑，返回boolean，为true时添加到数组里面
        // 返回一个数组
        let num2 = num.filter(function (n) {
            return n < 50;
        });
        console.log(num2);
        //使用map函数
        // 传入一个函数，返回的值添加到数组里面
        // 返回数组
        let num3 = num2.map(function (n) {
            return n * 2;
        });
        console.log(num3);
        //使用reduce函数
        // 传入一个函数和初始值，函数第一个参数是前一个值，第二个是遍历的第n个对象
        // 返回一个数值
        // 当不写初始值时，默认为0
        let total = num3.reduce(function (preValue, n) {
            return preValue + n;
        }, 0);
        console.log(total);

        // 以上函数可以一起使用
        let total2 = num.filter(function (n) {
            return n < 50;
        }).map(function (n) {
            return n * 2;
        }).reduce(function (preValue, n) {
            return preValue + n;
        }, 0);
        console.log(total2);

        /* 使用箭头表达式=> */
        // 左侧是函数传入的值，右侧是函数返回的值
        // 传入多个参数需要写()
        let total3 = num.filter(n => n < 50).map(n => n * 2).reduce((preValue, n) => preValue + n, 0);
        console.log(total3);

        /* 箭头函数拓展 */
        // 箭头函数主要用在：传入参数是函数时
        // 原始函数写写法
        const a1 = function () {
            return 'a1';
        }
        console.log(a1());
        // 箭头函数无参写法
        const a2 = () => {
            return 'a2';
        }
        console.log(a2());
        // 只有1个参数，可以省略括号
        const a3 = n => {
            return n / 2;
        }
        console.log(a3(88));
        // 多个参数
        const a4 = (m, n) => {
            return m + n;
        }
        console.log(a4(3, 6));
        // 只有1行代码，可以省略return和大括号
        const a5 = (m, n) => m * n;
        console.log(a5(5, 7));
        // 特殊：只有1个参数，1行代码
        const a6 = n => n * n;
        console.log(a6(9));

        /* prototype原型 */
        // Object.prototype可以向所有变量加上一个属性
        const aaa = {
            name: 'aaa'
        }
        Object.prototype.aaa = aaa;
        const bbb = 'bbb';
        console.log('Object.prototype.aaa');
        console.log(bbb.aaa);

        /* 箭头函数中this的指向 */
        // this是由内向外查找，直到找到this停止
        // function ()是由Window回调的，所以是Window
        // () =>不是回调，setTimeout中没有this，a()中有this是obj1
        const obj1 = {
            a() {
                console.log(this);  //obj1

                setTimeout(function () {
                    console.log(this);  //Window
                })
                setTimeout(() => {
                    console.log(this);  //obj1
                })

                setTimeout(function () {
                    setTimeout(function () {
                        console.log(this);  //Window
                    })
                })
                setTimeout(function () {
                    setTimeout(() => {
                        console.log(this);  //Window
                    })
                })

                setTimeout(() => {
                    setTimeout(function () {
                        console.log(this);  //Window
                    })
                })
                setTimeout(() => {
                    setTimeout(() => {
                        console.log(this);  //obj1
                    })
                })
            }
        }
        obj1.a();

        /* Promise的基本使用 */
        // 如果要发送多个嵌套异步请求（比如ajax），不进行管理代码逻辑会非常乱

        // 默认方式（使用setTimeout代替ajax）
        // 第一层
        setTimeout(() => {
            console.log('1默认方式');
            // 第二层
            setTimeout(() => {
                console.log('2默认方式');
                // 第三层
                setTimeout(() => {
                    console.log('3默认方式');
                }, 100)
            }, 100)
        }, 1000)

        // 使用Promise进行管理（异步）（使用setTimeout代替ajax）
        // Promise是一个对象，接收2个参数
        // resolve(解决)和reject(拒绝)（可选）是函数，可以表示是否符合条件
        // 如果是resolve则进行then(然后)，是reject进行catch(捕获)
        // 第一层
        new Promise((resolve, reject) => {
            // 这里写ajax请求，不要写业务代码
            // 一般成功调用resolve，失败调用reject
            // 第一层请求
            setTimeout(() => {
                // 可以传递数据
                resolve('1PromiseOk')
                // reject('1PromiseErr')
            }, 2000)
            // 第一层成功
        }).then(data => {
            // 如果调用了resolve会执行then
            // then接收函数参数，把成功的业务代码写这里
            console.log(data);
            // 第二层
            return new Promise((resolve, reject) => {
                // 第二层请求
                setTimeout(() => {
                    // 如果某一层调用了reject方法，则后面的不会执行
                    // reject('2PromiseErr')
                    resolve('2PromiseOk')
                }, 100)
            })
            // 第二层成功
        }).then(data => {
            console.log(data);
            // 第三层
            return new Promise((resolve, reject) => {
                // 第三层请求
                setTimeout(() => {
                    // resolve('3PromiseOk')
                    reject('3PromiseErr')
                }, 100)
            })
            // 第三层成功
        }).then(data => {
            console.log(data);
            // 所有的失败
        }).catch(err => {
            // 如果调用了reject会执行catch
            // catch接收函数参数，把失败业务代码写这里
            console.log(err);
        })
        // then还可以传递2个参数，第一个是resolve，第二个是reject
        // }).then(() => {
        //     console.log(data);
        // }, err => {
        //     console.log(err);
        // })

        // 使用Promise进行管理（非异步）
        new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('非异步1PromiseOk')
            }, 3000)
        }).then(data => {
            console.log(data);
            // 不进行异步请求时，可以直接返回Promise.resolve或Promise.reject
            return Promise.resolve('非异步2PromiseOk')
            // return Promise.reject('非异步2PromiseErr')
        }).then(data => {
            console.log(data);
            // 还可以把Promise.resolve和Promise.reject简写为return和throw
            // return '非异步3PromiseOk'
            throw '非异步3PromiseErr'
        }).then(data => {
            console.log(data);
        }).catch(err => {
            console.log(err);
        })

        // 使用普通方法进行多请求处理（比较麻烦）
        // 保存请求是否返回结果
        let isR1 = false
        let isR2 = false
        // 请求1
        setTimeout(() => {
            console.log('1多请求');
            isR1 = true
            handleR()
        }, 4000)
        // 请求2
        setTimeout(() => {
            console.log('2多请求');
            isR2 = true
            handleR()
        }, 4100)
        // 请求处理
        function handleR() {
            if (isR1 && isR2) {
                console.log('handleR');
            }
        }

        // 使用Promise进行管理（多请求）
        // 传入一个数组，数组里是Promise对象
        Promise.all([
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('1Promise多请求');
                    resolve('Promise.all.Ok1')
                    // reject('Promise.all.Err1')
                }, 5000)
            }),
            new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('2Promise多请求');
                    resolve('Promise.all.Ok2')
                    // reject('Promise.all.Err2')
                }, 5100)
            })
        ]).then(results => {
            // 接收一个结果集
            console.log(results);
        }).catch(err => {
            // 捕获异常
            console.log(err);
        })

        /* 数组、对象的解构 */
        // 数组解构
        {
            function f1() {
                return [1, 2, 3];
            }
            let [a, b, c] = f1();
            console.log(a, b, c); // 1  2  3
            let [d, , f] = f1();
            console.log(d, f); // 1  3
            let [e,] = f1();
            console.log(e); // 1
        }
        // 对象解构
        {
            function f2() {
                return {
                    a: '1',
                    b: '2',
                    c: '3'
                };
            }
            // 原变量名：新变量名
            let { a: x, b: y, c: z } = f2();
            console.log(x, y, z); // 1  2  3
            // 简写
            let { a, b, c } = f2();
            console.log(a, b, c); // 1  2  3
        }

        /* 数组的特殊用法 */
        {
            let a = [1, 2, 3];
            let b = [4, 5, 6];
            // 需求：把b插入到a后
            // 做法1：使用for循环
            for (i of b) {
                a.push(i);
            }
            console.log(a);
            // 做法2：使用...语法，push可以接受多个值
            a.push(...b);
            console.log(a);
            // 例如a.push(9,8,7);是把9,8,7插入到a后
            a.push(9, 8, 7);
            console.log(a);
        }
    </script>
</body>

</html>